package com.piggy.logi.sys.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.net.MediaType;
import com.piggy.common.core.constant.SecurityConstants;
import com.piggy.common.core.domain.R;
import com.piggy.common.core.exception.GlobalException;
import com.piggy.common.core.utils.PageUtils;
import com.piggy.common.core.utils.sql.SqlUtil;
import com.piggy.common.core.web.page.TableDataInfo;
import com.piggy.logi.sys.bo.LogiWaybillInvoiceEditBo;
import com.piggy.logi.sys.bo.LogiWaybillInvoiceQueryBo;
import com.piggy.logi.sys.bo.LogiWaybillIdsBo;
import com.piggy.logi.sys.domain.LogiSysBaseConfig;
import com.piggy.logi.sys.domain.LogiWaybill;
import com.piggy.logi.sys.domain.LogiWaybillInvoice;
import com.piggy.logi.sys.domain.LogiWaybillReceivable;
import com.piggy.logi.sys.enums.*;
import com.piggy.logi.sys.mapper.LogiWaybillInvoiceMapper;
import com.piggy.logi.sys.service.ILogiSysBaseConfigService;
import com.piggy.logi.sys.service.ILogiWaybillInvoiceService;
import com.piggy.logi.sys.service.ILogiWaybillReceivableService;
import com.piggy.logi.sys.service.ILogiWaybillService;
import com.piggy.logi.sys.vo.LogiWaybillExtVo;
import com.piggy.logi.sys.vo.LogiWaybillInvoiceVo;
import com.piggy.sys.api.RemoteOssService;
import com.piggy.sys.api.RemoteSmsService;
import com.piggy.sys.api.bo.OssFileUploadBo;
import com.piggy.sys.api.bo.SysEmailBo;
import com.piggy.sys.api.vo.SysOssVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Base64Utils;

import javax.annotation.Resource;
import javax.imageio.*;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.ImageOutputStream;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.util.List;

/**
 * 发票管理Service业务层处理
 *
 * @author fengxy
 * @date 2023/10/17 21:54
 **/
@Slf4j
@Service
public class LogiWaybillInvoiceServiceImpl extends ServiceImpl<LogiWaybillInvoiceMapper, LogiWaybillInvoice> implements ILogiWaybillInvoiceService {

    @Resource
    private ILogiWaybillService logiWaybillService;

    @Resource
    private RemoteOssService remoteOssService;

    @Resource
    private RemoteSmsService remoteSmsService;

    @Resource
    private ILogiSysBaseConfigService logiSysBaseConfigService;

    @Resource
    private ILogiWaybillReceivableService logiWaybillReceivableService;

    @Override
    public TableDataInfo<LogiWaybillInvoiceVo> queryPageList(LogiWaybillInvoiceQueryBo bo) {
        return PageUtils.buildDataInfo(pageVo(PageUtils.buildPagePlus(bo, true), buildQueryWrapper(bo), LogiWaybillInvoiceVo.class));
    }

    @Override
    public String getElectronInvoice(Long waybillId) {
        LogiWaybillInvoice invoice = getVoOne(Wrappers.lambdaQuery(LogiWaybillInvoice.class)
                .eq(LogiWaybillInvoice::getWaybillId, waybillId), LogiWaybillInvoice.class);
        if (ObjectUtil.isNull(invoice)) {
            throw new GlobalException("发票信息不存在");
        }
        R<SysOssVo> oss = remoteOssService.getSysOssById(invoice.getElectronInvoice(), SecurityConstants.INNER);
        if (ObjectUtil.isNull(oss) || ObjectUtil.isNull(oss.getData())) {
            throw new GlobalException("电子发票获取失败或不存在");
        } else {
            return oss.getData().getUrl();
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void insertByAddBo(LogiWaybillInvoiceEditBo bo) {
        // 查询运单信息
        LogiWaybillExtVo waybillExtVo = logiWaybillService.queryById(bo.getWaybillId());
        if (ObjectUtil.isNull(waybillExtVo)) {
            throw new GlobalException("运单数据不存在");
        }
        // 判断运单是否已完成对账
        LogiSysBaseConfig sysBaseConfig = logiSysBaseConfigService.getBaseConfig(bo.getDeptId());
        if (sysBaseConfig.getCheckBillInvoice() == 1) {
            LogiWaybillReceivable receivable = logiWaybillReceivableService.getOne(Wrappers.lambdaQuery(LogiWaybillReceivable.class)
                    .eq(LogiWaybillReceivable::getWaybillId, waybillExtVo.getWaybillId()));
            if (receivable.getCheckBillStatus() == CheckBillStatusEnum.NOT_CHECK.getCode()) {
                throw new GlobalException("运单必须完成对账才可开票");
            }
        }
        // 判断运单状态是否为已完成
        if (waybillExtVo.getStatus() != WaybillStatusEnum.COMPLETED.getCode()) {
            throw new GlobalException("请选择已完成的运单");
        }
        // 整理发票信息
        LogiWaybillInvoice add = BeanUtil.toBean(bo, LogiWaybillInvoice.class);
        add.setStatus(InvoiceStatusEnum.INVOICED.getCode());
//        add.setStatus(InvoiceStatusEnum.INVOICING.getCode());
        add.setOutCode(waybillExtVo.getOutCode());
        add.setBatchNo("P" + IdUtil.getSnowflakeNextIdStr());
        add.setApplyTime(DateUtil.date());
        // 生成电子发票
        if (bo.getType() == InvoiceTypeEnum.ELECTRON.getCode()) {
            Long electronInvoice = genElectronInvoice(waybillExtVo, add);
            if (ObjectUtil.isNull(electronInvoice)) {
                log.error("生成电子发票失败");
            }
            add.setElectronInvoice(String.valueOf(electronInvoice));
        }
        // 修改运单的发票状态：已开票
        boolean success = logiWaybillService.update(Wrappers.<LogiWaybill>lambdaUpdate()
                .set(LogiWaybill::getInvoiceStatus, WaybillInvoiceStatusEnum.INVOICED.getCode())
                .eq(LogiWaybill::getWaybillId, waybillExtVo.getWaybillId()));
        if (!success) {
            throw new GlobalException("修改运单的发票状态为已开票失败");
        }
        // 新增开票记录
        success = save(add);
        if (!success) {
            throw new GlobalException("新增开票记录失败");
        }
    }

    @Override
    public void noNeed(LogiWaybillIdsBo bo) {
        // 查询运单信息
        List<LogiWaybill> bills = logiWaybillService.listByIds(bo.getWaybillIds());
        if (ObjectUtil.isNull(bills)) {
            throw new GlobalException("运单数据不存在");
        }
        // 判断运单开票状态，如果是已开票或暂不开票，则不允许操作
        List<Long> billIds = CollUtil.newArrayList();
        for (LogiWaybill bill : bills) {
            if (bill.getInvoiceStatus() == WaybillInvoiceStatusEnum.INVOICED.getCode() ||
                    bill.getInvoiceStatus() == WaybillInvoiceStatusEnum.NO_NEED.getCode()) {
                throw new GlobalException("运单编号：" + bill.getOutCode() + "的开票状态为已开票或暂不开票，请检查");
            }
            billIds.add(bill.getWaybillId());
        }
        // 修改运单的发票状态：暂不开票
        boolean success = logiWaybillService.update(Wrappers.<LogiWaybill>lambdaUpdate()
                .set(LogiWaybill::getInvoiceStatus, WaybillInvoiceStatusEnum.NO_NEED.getCode())
                .in(LogiWaybill::getWaybillId, billIds));
        if (!success) {
            throw new GlobalException("修改运单的发票状态为暂不开票失败");
        }
    }

    @Override
    public void recover(LogiWaybillIdsBo bo) {
        // 查询运单信息
        List<LogiWaybill> bills = logiWaybillService.listByIds(bo.getWaybillIds());
        if (ObjectUtil.isNull(bills)) {
            throw new GlobalException("运单数据不存在");
        }
        // 判断运单开票状态，如果是已开票或未开票，则不允许操作
        List<Long> billIds = CollUtil.newArrayList();
        for (LogiWaybill bill : bills) {
            if (bill.getInvoiceStatus() == WaybillInvoiceStatusEnum.INVOICED.getCode() ||
                    bill.getInvoiceStatus() == WaybillInvoiceStatusEnum.NO_INVOICE.getCode()) {
                throw new GlobalException("运单编号：" + bill.getOutCode() + "的开票状态为已开票或未开票，请检查");
            }
            billIds.add(bill.getWaybillId());
        }
        // 修改运单的发票状态：未开票
        boolean success = logiWaybillService.update(Wrappers.<LogiWaybill>lambdaUpdate()
                .set(LogiWaybill::getInvoiceStatus, WaybillInvoiceStatusEnum.NO_INVOICE.getCode())
                .in(LogiWaybill::getWaybillId, billIds));
        if (!success) {
            throw new GlobalException("修改运单的发票状态为未开票失败");
        }
    }

    @Override
    public void sendEmail(Long id, String email) {
        // 查询发票记录
        LogiWaybillInvoice invoice = getById(id);
        if (ObjectUtil.isNull(invoice)) {
            throw new GlobalException("发票信息不存在");
        }
        SysEmailBo bo = new SysEmailBo();
        bo.setContent("公司名称：{}，公司税号：{}，发票总额：{}");
        bo.setSubject("运单" + invoice.getOutCode() + "发票");
        bo.setParams(CollUtil.newArrayList(invoice.getCompanyName(), invoice.getCompanyTaxNo(), invoice.getInvoiceTotal().toString()));
        bo.setReceiver(email);
        try {
            R<SysOssVo> ossVo = remoteOssService.getSysOssById(invoice.getElectronInvoice(), SecurityConstants.INNER);
            SysOssVo vo = ossVo.getData();
            File file = HttpUtil.downloadFileFromUrl(vo.getUrl(), FileUtil.createTempFile("invoice", "." + vo.getFileType(), true));
            bo.setFiles(new File[]{file});
        } catch (Exception e) {
            throw new GlobalException("添加发票附件失败");
        }
        remoteSmsService.sendMail(bo, SecurityConstants.INNER);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void reInvoice(LogiWaybillInvoiceEditBo bo) {
        // 查询发票记录
        LogiWaybillInvoice update = getById(bo.getId());
        if (ObjectUtil.isNull(update)) {
            throw new GlobalException("发票信息不存在");
        }
        // 判断运单是否已完成对账
        LogiSysBaseConfig sysBaseConfig = logiSysBaseConfigService.getBaseConfig(update.getDeptId());
        if (sysBaseConfig.getCheckBillInvoice() == 1) {
            LogiWaybillReceivable receivable = logiWaybillReceivableService.getOne(Wrappers.lambdaQuery(LogiWaybillReceivable.class)
                    .eq(LogiWaybillReceivable::getWaybillId, update.getWaybillId()));
            if (receivable.getCheckBillStatus() == CheckBillStatusEnum.NOT_CHECK.getCode()) {
                throw new GlobalException("运单必须完成对账才可开票");
            }
        }
        // 修改开票记录
        BeanUtil.copyProperties(bo, update);
        // 查询运单信息
        LogiWaybillExtVo waybillExtVo = logiWaybillService.queryById(bo.getWaybillId());
        if (ObjectUtil.isNull(waybillExtVo)) {
            throw new GlobalException("运单数据不存在");
        }
        if (update.getType() == InvoiceTypeEnum.ELECTRON.getCode()) {
            Long electronInvoice = genElectronInvoice(waybillExtVo, update);
            if (ObjectUtil.isNull(electronInvoice)) {
                log.error("生成电子发票失败");
            }
            update.setElectronInvoice(String.valueOf(electronInvoice));
        }
        boolean success = updateById(update);
        if (!success) {
            throw new GlobalException("修改失败");
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void obsolete(Long id) {
        // 查询发票信息
        LogiWaybillInvoice invoice = getById(id);
        if (ObjectUtil.isNull(invoice)) {
            throw new GlobalException("发票信息不存在");
        }
        // 判断发票状态，如果是已作废，则不允许作废，其他状态均可操作
        if (invoice.getStatus() == InvoiceStatusEnum.OBSOLETE.getCode()) {
            throw new GlobalException("发票已作废，请检查");
        }
        // 修改运单的发票状态：未开票
        boolean success = logiWaybillService.update(Wrappers.<LogiWaybill>lambdaUpdate()
                .set(LogiWaybill::getInvoiceStatus, WaybillInvoiceStatusEnum.NO_INVOICE.getCode())
                .eq(LogiWaybill::getWaybillId, invoice.getWaybillId()));
        if (!success) {
            throw new GlobalException("修改运单的发票状态为未开票失败");
        }
        // 修改发票状态为：已作废
        invoice.setStatus(InvoiceStatusEnum.OBSOLETE.getCode());
        success = updateById(invoice);
        if (!success) {
            throw new GlobalException("修改发票状态为已作废失败");
        }
    }

    /**
     * 组装查询条件
     **/
    private LambdaQueryWrapper<LogiWaybillInvoice> buildQueryWrapper(LogiWaybillInvoiceQueryBo bo) {
        LambdaQueryWrapper<LogiWaybillInvoice> lqw = Wrappers.lambdaQuery();
        lqw.like(bo.getOutCode() != null, LogiWaybillInvoice::getOutCode, bo.getOutCode());
        lqw.like(bo.getBatchNo() != null, LogiWaybillInvoice::getBatchNo, bo.getBatchNo());
        lqw.eq(bo.getStatus() != null, LogiWaybillInvoice::getStatus, bo.getStatus());
        // 客户名称筛选
        if (StrUtil.isNotBlank(bo.getCustomerName())) {
            String existsSql = "select 1 from logi_customer where logi_customer.customer_id = logi_waybill_invoice.customer_id" +
                    " and logi_customer.customer_name like '%" + bo.getCustomerName() + "%'";
            lqw.exists(existsSql);
        }
        lqw.between(ObjectUtil.isAllNotEmpty(bo.getApplyStartDate(), bo.getApplyEndDate()), LogiWaybillInvoice::getApplyTime, bo.getApplyStartDate(), bo.getApplyEndDate());
        SqlUtil.dataPermissionFilter(lqw, bo.getDeptId());
        return lqw;
    }

    /**
     * 生成电子发票
     **/
    private Long genElectronInvoice(LogiWaybillExtVo waybillExtVo, LogiWaybillInvoice invoice) {
        // 电子发票模板添加发票信息
        byte[] imageBytes = drawImage(
                CollUtil.newArrayList(invoice.getBatchNo(), DatePattern.NORM_DATETIME_FORMAT.format(invoice.getApplyTime()),
                        waybillExtVo.getDeliveryName(), waybillExtVo.getDeliveryContactTel(), waybillExtVo.getPayableTotalFee().toString(),
                        invoice.getCompanyName(), invoice.getCompanyTaxNo(), invoice.getCompanyMobile(), invoice.getAccountNumber(),
                        invoice.getRemark(), invoice.getCompanyName(), invoice.getInvoiceParty()),
                CollUtil.newArrayList(700, 700, 160, 160, 257, 160, 160, 160, 160, 531, 103, 509),
                CollUtil.newArrayList(67, 83, 122, 160, 356, 384, 401, 420, 434, 412, 458, 458),
                ResourceUtil.getStream("static/invoice-template.png"));
        if (ObjectUtil.isNull(imageBytes)) {
            log.error("电子发票模板添加发票信息失败");
            return null;
        }
        // 将图片转为pdf
        byte[] pdfBytes = imageToPdf(imageBytes, MediaType.PNG.subtype());
        if (ObjectUtil.isNull(pdfBytes)) {
            log.error("电子发票转为pdf失败");
            return null;
        }
        // 上传文件
        OssFileUploadBo bo = new OssFileUploadBo();
        bo.setFileName(IdWorker.get32UUID() + StrUtil.DOT + MediaType.PDF.subtype());
        bo.setContent(Base64Utils.encodeToString(pdfBytes));
        R<SysOssVo> vo = remoteOssService.uploadToCloud(bo, SecurityConstants.INNER);
        if (ObjectUtil.isNull(vo) || ObjectUtil.isNull(vo.getData())) {
            log.error("电子发票上传失败");
            return null;
        }
        return vo.getData().getId();
    }

    /**
     * 将文字画到图片
     **/
    public static byte[] drawImage(List<String> contentList, List<Integer> xList, List<Integer> yList, InputStream inputStream) {
        try {
            // 文件输出流
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            // 源图片
            Image srcImg = ImageIO.read(inputStream);
            // 原图宽度
            int width = srcImg.getWidth(null);
            // 原图高度
            int height = srcImg.getHeight(null);
            // 通过原图计算出字体大小
            Font font = Font.createFont(Font.TRUETYPE_FONT, ResourceUtil.getStream("static/simhei.ttf"));
            font = font.deriveFont(Font.BOLD, 10);
            // 获取缓冲区图像
            BufferedImage buffImg = new BufferedImage(srcImg.getWidth(null), srcImg.getHeight(null),
                    BufferedImage.TYPE_INT_RGB);
            // 得到画笔对象
            Graphics2D g = buffImg.createGraphics();
            // 设置对线段的锯齿状边缘处理
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            // 渲染原图
            g.drawImage(srcImg, 0, 0, width, height, null);
            // 设置文字颜色
            g.setColor(Color.black);
            // 设置文字Font
            g.setFont(font);
            // 设置文字透明度
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, 0.8f));
            // 添加文字
            for (int i = 0; i < contentList.size(); i++) {
                g.drawString(contentList.get(i), xList.get(i), yList.get(i));
            }
            g.dispose();
            ImageWriter imageWriter = ImageIO.getImageWritersBySuffix("png").next();
            ImageOutputStream ios = ImageIO.createImageOutputStream(out);
            imageWriter.setOutput(ios);
            ImageWriteParam imageWriteParam = imageWriter.getDefaultWriteParam();
            if (imageWriteParam.canWriteCompressed()) {
                // 这一行固定这样写，跟下一行搭配
                imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                // 跟上一行搭配，设置输出图片文件的质量
                imageWriteParam.setCompressionQuality(0.947F);
            }
            IIOImage iioImage = new IIOImage(buffImg, null, null);
            // 写文件
            imageWriter.write(null, iioImage, imageWriteParam);
            out.close();
            imageWriter.dispose();
            return out.toByteArray();
        } catch (Exception e) {
            log.error("将文字画到图片失败：", e);
            return null;
        }
    }

    /**
     * 图片转pdf
     *
     * @author fengxy
     * @date 2023/10/23 18:03
     **/
    public static byte[] imageToPdf(byte[] imageBytes, String imageSuffix) {
        try {
            // 文件输出流
            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
            // 获取文件
            PDDocument document = new PDDocument();
            ImageReader reader = ImageIO.getImageReadersByFormatName(imageSuffix).next();
            ImageInputStream input = ImageIO.createImageInputStream(IoUtil.toStream(imageBytes));
            reader.setInput(input, true);
            // 设置画布宽高
            int width = reader.getWidth(0);
            int height = reader.getHeight(0);
            PDPage pdPage = new PDPage(new PDRectangle(width, height));
            // 根据图片大小动态生成pdf大小
            document.addPage(pdPage);
            PDImageXObject pdImageXObject = PDImageXObject.createFromByteArray(document, imageBytes, "构建图片错误");
            PDPageContentStream contentStream = new PDPageContentStream(document, pdPage);
            // 写入图片
            contentStream.drawImage(pdImageXObject, 0, 0);
            contentStream.close();
            // pdf输出
            document.save(out);
            document.close();
            out.close();
            return out.toByteArray();
        } catch (Exception e) {
            log.error("图片转pdf失败：", e);
            return null;
        }
    }
}
